/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * Model loading and caching. Includes the .bsp file format
 *
 * =======================================================================
 */ 

#include "header/local.h"

#define MAX_MOD_KNOWN   512

model_t *loadmodel;
int modfilelen;
byte mod_novis [ MAX_MAP_LEAFS / 8 ];
model_t mod_known [ MAX_MOD_KNOWN ];
int mod_numknown;
int registration_sequence;
byte *mod_base;

void LoadSP2 ( model_t *mod, void *buffer );
void Mod_LoadBrushModel ( model_t *mod, void *buffer );
void LoadMD2 ( model_t *mod, void *buffer );
model_t *Mod_LoadModel ( model_t *mod, qboolean crash );
void LM_BuildPolygonFromSurface ( msurface_t *fa );
void LM_CreateSurfaceLightmap ( msurface_t *surf );
void LM_EndBuildingLightmaps ( void );
void LM_BeginBuildingLightmaps ( model_t *m );

/* the inline * models from the current map are kept seperate */
model_t mod_inline [ MAX_MOD_KNOWN ];

mleaf_t *
Mod_PointInLeaf ( vec3_t p, model_t *model )
{
	mnode_t     *node;
	float d;
	cplane_t    *plane;

	if ( !model || !model->nodes )
	{
		ri.Sys_Error( ERR_DROP, "Mod_PointInLeaf: bad model" );
	}

	node = model->nodes;

	while ( 1 )
	{
		if ( node->contents != -1 )
		{
			return ( (mleaf_t *) node );
		}

		plane = node->plane;
		d = DotProduct( p, plane->normal ) - plane->dist;

		if ( d > 0 )
		{
			node = node->children [ 0 ];
		}
		else
		{
			node = node->children [ 1 ];
		}
	}

	return ( NULL ); /* never reached */
}

byte *
Mod_DecompressVis ( byte *in, model_t *model )
{
	static byte decompressed [ MAX_MAP_LEAFS / 8 ];
	int c;
	byte    *out;
	int row;

	row = ( model->vis->numclusters + 7 ) >> 3;
	out = decompressed;

	if ( !in )
	{   
		/* no vis info, so make all visible */
		while ( row )
		{
			*out++ = 0xff;
			row--;
		}

		return ( decompressed );
	}

	do
	{
		if ( *in )
		{
			*out++ = *in++;
			continue;
		}

		c = in [ 1 ];
		in += 2;

		while ( c )
		{
			*out++ = 0;
			c--;
		}
	}
	while ( out - decompressed < row );

	return ( decompressed );
}

byte *
Mod_ClusterPVS ( int cluster, model_t *model )
{
	if ( ( cluster == -1 ) || !model->vis )
	{
		return ( mod_novis );
	}

	return ( Mod_DecompressVis( (byte *) model->vis + model->vis->bitofs [ cluster ] [ DVIS_PVS ],
					 model ) );
}

void
Mod_Modellist_f ( void )
{
	int i;
	model_t *mod;
	int total;

	total = 0;
	ri.Con_Printf( PRINT_ALL, "Loaded models:\n" );

	for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ )
	{
		if ( !mod->name [ 0 ] )
		{
			continue;
		}

		ri.Con_Printf( PRINT_ALL, "%8i : %s\n", mod->extradatasize, mod->name );
		total += mod->extradatasize;
	}

	ri.Con_Printf( PRINT_ALL, "Total resident: %i\n", total );
}

void
Mod_Init ( void )
{
	memset( mod_novis, 0xff, sizeof ( mod_novis ) );
}

/*
 * Loads in a model for the given name
 */
model_t *
Mod_ForName ( char *name, qboolean crash )
{
	model_t *mod;
	unsigned *buf;
	int i;

	if ( !name [ 0 ] )
	{
		ri.Sys_Error( ERR_DROP, "Mod_ForName: NULL name" );
	}

	/* inline models are grabbed only from worldmodel */
	if ( name [ 0 ] == '*' )
	{
		i = atoi( name + 1 );

		if ( ( i < 1 ) || !r_worldmodel || ( i >= r_worldmodel->numsubmodels ) )
		{
			ri.Sys_Error( ERR_DROP, "bad inline model number" );
		}

		return ( &mod_inline [ i ] );
	}

	/* search the currently loaded models */
	for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ )
	{
		if ( !mod->name [ 0 ] )
		{
			continue;
		}

		if ( !strcmp( mod->name, name ) )
		{
			return ( mod );
		}
	}

	/* find a free model slot spot */
	for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ )
	{
		if ( !mod->name [ 0 ] )
		{
			break; /* free spot */
		}
	}

	if ( i == mod_numknown )
	{
		if ( mod_numknown == MAX_MOD_KNOWN )
		{
			ri.Sys_Error( ERR_DROP, "mod_numknown == MAX_MOD_KNOWN" );
		}

		mod_numknown++;
	}

	strcpy( mod->name, name );

	/* load the file */
	modfilelen = ri.FS_LoadFile( mod->name, (void **) &buf );

	if ( !buf )
	{
		if ( crash )
		{
			ri.Sys_Error( ERR_DROP, "Mod_NumForName: %s not found", mod->name );
		}

		memset( mod->name, 0, sizeof ( mod->name ) );
		return ( NULL );
	}

	loadmodel = mod;

	/* call the apropriate loader */
	switch ( LittleLong( *(unsigned *) buf ) )
	{
		case IDALIASHEADER:
			loadmodel->extradata = Hunk_Begin( 0x200000 );
			LoadMD2( mod, buf );
			break;

		case IDSPRITEHEADER:
			loadmodel->extradata = Hunk_Begin( 0x10000 );
			LoadSP2( mod, buf );
			break;

		case IDBSPHEADER:
			loadmodel->extradata = Hunk_Begin( 0x1000000 );
			Mod_LoadBrushModel( mod, buf );
			break;

		default:
			ri.Sys_Error( ERR_DROP, "Mod_NumForName: unknown fileid for %s", mod->name );
			break;
	}

	loadmodel->extradatasize = Hunk_End();

	ri.FS_FreeFile( buf );

	return ( mod );
}

void
Mod_LoadLighting ( lump_t *l )
{
	if ( !l->filelen )
	{
		loadmodel->lightdata = NULL;
		return;
	}

	loadmodel->lightdata = Hunk_Alloc( l->filelen );
	memcpy( loadmodel->lightdata, mod_base + l->fileofs, l->filelen );
}

void
Mod_LoadVisibility ( lump_t *l )
{
	int i;

	if ( !l->filelen )
	{
		loadmodel->vis = NULL;
		return;
	}

	loadmodel->vis = Hunk_Alloc( l->filelen );
	memcpy( loadmodel->vis, mod_base + l->fileofs, l->filelen );

	loadmodel->vis->numclusters = LittleLong( loadmodel->vis->numclusters );

	for ( i = 0; i < loadmodel->vis->numclusters; i++ )
	{
		loadmodel->vis->bitofs [ i ] [ 0 ] = LittleLong( loadmodel->vis->bitofs [ i ] [ 0 ] );
		loadmodel->vis->bitofs [ i ] [ 1 ] = LittleLong( loadmodel->vis->bitofs [ i ] [ 1 ] );
	}
}

void
Mod_LoadVertexes ( lump_t *l )
{
	dvertex_t   *in;
	mvertex_t   *out;
	int i, count;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->vertexes = out;
	loadmodel->numvertexes = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		out->position [ 0 ] = LittleFloat( in->point [ 0 ] );
		out->position [ 1 ] = LittleFloat( in->point [ 1 ] );
		out->position [ 2 ] = LittleFloat( in->point [ 2 ] );
	}
}

float
Mod_RadiusFromBounds ( vec3_t mins, vec3_t maxs )
{
	int i;
	vec3_t corner;

	for ( i = 0; i < 3; i++ )
	{
		corner [ i ] = fabs( mins [ i ] ) > fabs( maxs [ i ] ) ? fabs( mins [ i ] ) : fabs( maxs [ i ] );
	}

	return ( VectorLength( corner ) );
}

void
Mod_LoadSubmodels ( lump_t *l )
{
	dmodel_t    *in;
	mmodel_t    *out;
	int i, j, count;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->submodels = out;
	loadmodel->numsubmodels = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		for ( j = 0; j < 3; j++ )
		{   
			/* spread the mins / maxs by a pixel */
			out->mins [ j ] = LittleFloat( in->mins [ j ] ) - 1;
			out->maxs [ j ] = LittleFloat( in->maxs [ j ] ) + 1;
			out->origin [ j ] = LittleFloat( in->origin [ j ] );
		}

		out->radius = Mod_RadiusFromBounds( out->mins, out->maxs );
		out->headnode = LittleLong( in->headnode );
		out->firstface = LittleLong( in->firstface );
		out->numfaces = LittleLong( in->numfaces );
	}
}

void
Mod_LoadEdges ( lump_t *l )
{
	dedge_t *in;
	medge_t *out;
	int i, count;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( ( count + 1 ) * sizeof ( *out ) );

	loadmodel->edges = out;
	loadmodel->numedges = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		out->v [ 0 ] = (unsigned short) LittleShort( in->v [ 0 ] );
		out->v [ 1 ] = (unsigned short) LittleShort( in->v [ 1 ] );
	}
}

void
Mod_LoadTexinfo ( lump_t *l )
{
	texinfo_t *in;
	mtexinfo_t *out, *step;
	int i, j, count;
	char name [ MAX_QPATH ];
	int next;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->texinfo = out;
	loadmodel->numtexinfo = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		for ( j = 0; j < 8; j++ )
		{
			out->vecs [ 0 ] [ j ] = LittleFloat( in->vecs [ 0 ] [ j ] );
		}

		out->flags = LittleLong( in->flags );
		next = LittleLong( in->nexttexinfo );

		if ( next > 0 )
		{
			out->next = loadmodel->texinfo + next;
		}
		else
		{
			out->next = NULL;
		}

		Com_sprintf( name, sizeof ( name ), "textures/%s.wal", in->texture );

		out->image = R_FindImage( name, it_wall );

		if ( !out->image )
		{
			ri.Con_Printf( PRINT_ALL, "Couldn't load %s\n", name );
			out->image = r_notexture;
		}
	}

	/* count animation frames */
	for ( i = 0; i < count; i++ )
	{
		out = &loadmodel->texinfo [ i ];
		out->numframes = 1;

		for ( step = out->next; step && step != out; step = step->next )
		{
			out->numframes++;
		}
	}
}

/*
 * Fills in s->texturemins[] and s->extents[]
 */
void
Mod_CalcSurfaceExtents ( msurface_t *s )
{
	float mins [ 2 ], maxs [ 2 ], val;
	int i, j, e;
	mvertex_t   *v;
	mtexinfo_t  *tex;
	int bmins [ 2 ], bmaxs [ 2 ];

	mins [ 0 ] = mins [ 1 ] = 999999;
	maxs [ 0 ] = maxs [ 1 ] = -99999;

	tex = s->texinfo;

	for ( i = 0; i < s->numedges; i++ )
	{
		e = loadmodel->surfedges [ s->firstedge + i ];

		if ( e >= 0 )
		{
			v = &loadmodel->vertexes [ loadmodel->edges [ e ].v [ 0 ] ];
		}
		else
		{
			v = &loadmodel->vertexes [ loadmodel->edges [ -e ].v [ 1 ] ];
		}

		for ( j = 0; j < 2; j++ )
		{
			val = v->position [ 0 ] * tex->vecs [ j ] [ 0 ] +
				  v->position [ 1 ] * tex->vecs [ j ] [ 1 ] +
				  v->position [ 2 ] * tex->vecs [ j ] [ 2 ] +
				  tex->vecs [ j ] [ 3 ];

			if ( val < mins [ j ] )
			{
				mins [ j ] = val;
			}

			if ( val > maxs [ j ] )
			{
				maxs [ j ] = val;
			}
		}
	}

	for ( i = 0; i < 2; i++ )
	{
		bmins [ i ] = floor( mins [ i ] / 16 );
		bmaxs [ i ] = ceil( maxs [ i ] / 16 );

		s->texturemins [ i ] = bmins [ i ] * 16;
		s->extents [ i ] = ( bmaxs [ i ] - bmins [ i ] ) * 16;
	}
}

void
Mod_LoadFaces ( lump_t *l )
{
	dface_t     *in;
	msurface_t  *out;
	int i, count, surfnum;
	int planenum, side;
	int ti;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->surfaces = out;
	loadmodel->numsurfaces = count;

	currentmodel = loadmodel;

	LM_BeginBuildingLightmaps( loadmodel );

	for ( surfnum = 0; surfnum < count; surfnum++, in++, out++ )
	{
		out->firstedge = LittleLong( in->firstedge );
		out->numedges = LittleShort( in->numedges );
		out->flags = 0;
		out->polys = NULL;

		planenum = LittleShort( in->planenum );
		side = LittleShort( in->side );

		if ( side )
		{
			out->flags |= SURF_PLANEBACK;
		}

		out->plane = loadmodel->planes + planenum;

		ti = LittleShort( in->texinfo );

		if ( ( ti < 0 ) || ( ti >= loadmodel->numtexinfo ) )
		{
			ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: bad texinfo number" );
		}

		out->texinfo = loadmodel->texinfo + ti;

		Mod_CalcSurfaceExtents( out );

		/* lighting info */
		for ( i = 0; i < MAXLIGHTMAPS; i++ )
		{
			out->styles [ i ] = in->styles [ i ];
		}

		i = LittleLong( in->lightofs );

		if ( i == -1 )
		{
			out->samples = NULL;
		}
		else
		{
			out->samples = loadmodel->lightdata + i;
		}

		/* set the drawing flags */
		if ( out->texinfo->flags & SURF_WARP )
		{
			out->flags |= SURF_DRAWTURB;

			for ( i = 0; i < 2; i++ )
			{
				out->extents [ i ] = 16384;
				out->texturemins [ i ] = -8192;
			}

			R_SubdivideSurface( out ); /* cut up polygon for warps */
		}

		/* create lightmaps and polygons */
		if ( !( out->texinfo->flags & ( SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP ) ) )
		{
			LM_CreateSurfaceLightmap( out );
		}

		if ( !( out->texinfo->flags & SURF_WARP ) )
		{
			LM_BuildPolygonFromSurface( out );
		}
	}

	LM_EndBuildingLightmaps();
}

void
Mod_SetParent ( mnode_t *node, mnode_t *parent )
{
	node->parent = parent;

	if ( node->contents != -1 )
	{
		return;
	}

	Mod_SetParent( node->children [ 0 ], node );
	Mod_SetParent( node->children [ 1 ], node );
}

void
Mod_LoadNodes ( lump_t *l )
{
	int i, j, count, p;
	dnode_t     *in;
	mnode_t     *out;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->nodes = out;
	loadmodel->numnodes = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		for ( j = 0; j < 3; j++ )
		{
			out->minmaxs [ j ] = LittleShort( in->mins [ j ] );
			out->minmaxs [ 3 + j ] = LittleShort( in->maxs [ j ] );
		}

		p = LittleLong( in->planenum );
		out->plane = loadmodel->planes + p;

		out->firstsurface = LittleShort( in->firstface );
		out->numsurfaces = LittleShort( in->numfaces );
		out->contents = -1; /* differentiate from leafs */

		for ( j = 0; j < 2; j++ )
		{
			p = LittleLong( in->children [ j ] );

			if ( p >= 0 )
			{
				out->children [ j ] = loadmodel->nodes + p;
			}
			else
			{
				out->children [ j ] = (mnode_t *) ( loadmodel->leafs + ( -1 - p ) );
			}
		}
	}

	Mod_SetParent( loadmodel->nodes, NULL ); /* sets nodes and leafs */
}

void
Mod_LoadLeafs ( lump_t *l )
{
	dleaf_t     *in;
	mleaf_t     *out;
	int i, j, count, p;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->leafs = out;
	loadmodel->numleafs = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		for ( j = 0; j < 3; j++ )
		{
			out->minmaxs [ j ] = LittleShort( in->mins [ j ] );
			out->minmaxs [ 3 + j ] = LittleShort( in->maxs [ j ] );
		}

		p = LittleLong( in->contents );
		out->contents = p;

		out->cluster = LittleShort( in->cluster );
		out->area = LittleShort( in->area );

		out->firstmarksurface = loadmodel->marksurfaces +
								LittleShort( in->firstleafface );
		out->nummarksurfaces = LittleShort( in->numleaffaces );
	}
}

void
Mod_LoadMarksurfaces ( lump_t *l )
{
	int i, j, count;
	short       *in;
	msurface_t **out;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->marksurfaces = out;
	loadmodel->nummarksurfaces = count;

	for ( i = 0; i < count; i++ )
	{
		j = LittleShort( in [ i ] );

		if ( ( j < 0 ) || ( j >= loadmodel->numsurfaces ) )
		{
			ri.Sys_Error( ERR_DROP, "Mod_ParseMarksurfaces: bad surface number" );
		}

		out [ i ] = loadmodel->surfaces + j;
	}
}

void
Mod_LoadSurfedges ( lump_t *l )
{
	int i, count;
	int     *in, *out;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );

	if ( ( count < 1 ) || ( count >= MAX_MAP_SURFEDGES ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: bad surfedges count in %s: %i",
				loadmodel->name, count );
	}

	out = Hunk_Alloc( count * sizeof ( *out ) );

	loadmodel->surfedges = out;
	loadmodel->numsurfedges = count;

	for ( i = 0; i < count; i++ )
	{
		out [ i ] = LittleLong( in [ i ] );
	}
}

void
Mod_LoadPlanes ( lump_t *l )
{
	int i, j;
	cplane_t    *out;
	dplane_t    *in;
	int count;
	int bits;

	in = (void *) ( mod_base + l->fileofs );

	if ( l->filelen % sizeof ( *in ) )
	{
		ri.Sys_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size in %s", loadmodel->name );
	}

	count = l->filelen / sizeof ( *in );
	out = Hunk_Alloc( count * 2 * sizeof ( *out ) );

	loadmodel->planes = out;
	loadmodel->numplanes = count;

	for ( i = 0; i < count; i++, in++, out++ )
	{
		bits = 0;

		for ( j = 0; j < 3; j++ )
		{
			out->normal [ j ] = LittleFloat( in->normal [ j ] );

			if ( out->normal [ j ] < 0 )
			{
				bits |= 1 << j;
			}
		}

		out->dist = LittleFloat( in->dist );
		out->type = LittleLong( in->type );
		out->signbits = bits;
	}
}

void
Mod_LoadBrushModel ( model_t *mod, void *buffer )
{
	int i;
	dheader_t   *header;
	mmodel_t    *bm;

	loadmodel->type = mod_brush;

	if ( loadmodel != mod_known )
	{
		ri.Sys_Error( ERR_DROP, "Loaded a brush model after the world" );
	}

	header = (dheader_t *) buffer;

	i = LittleLong( header->version );

	if ( i != BSPVERSION )
	{
		ri.Sys_Error( ERR_DROP, "Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, BSPVERSION );
	}

	/* swap all the lumps */
	mod_base = (byte *) header;

	for ( i = 0; i < sizeof ( dheader_t ) / 4; i++ )
	{
		( (int *) header ) [ i ] = LittleLong( ( (int *) header ) [ i ] );
	}

	/* load into heap */
	Mod_LoadVertexes( &header->lumps [ LUMP_VERTEXES ] );
	Mod_LoadEdges( &header->lumps [ LUMP_EDGES ] );
	Mod_LoadSurfedges( &header->lumps [ LUMP_SURFEDGES ] );
	Mod_LoadLighting( &header->lumps [ LUMP_LIGHTING ] );
	Mod_LoadPlanes( &header->lumps [ LUMP_PLANES ] );
	Mod_LoadTexinfo( &header->lumps [ LUMP_TEXINFO ] );
	Mod_LoadFaces( &header->lumps [ LUMP_FACES ] );
	Mod_LoadMarksurfaces( &header->lumps [ LUMP_LEAFFACES ] );
	Mod_LoadVisibility( &header->lumps [ LUMP_VISIBILITY ] );
	Mod_LoadLeafs( &header->lumps [ LUMP_LEAFS ] );
	Mod_LoadNodes( &header->lumps [ LUMP_NODES ] );
	Mod_LoadSubmodels( &header->lumps [ LUMP_MODELS ] );
	mod->numframes = 2; /* regular and alternate animation */

	/* set up the submodels */
	for ( i = 0; i < mod->numsubmodels; i++ )
	{
		model_t *starmod;

		bm = &mod->submodels [ i ];
		starmod = &mod_inline [ i ];

		*starmod = *loadmodel;

		starmod->firstmodelsurface = bm->firstface;
		starmod->nummodelsurfaces = bm->numfaces;
		starmod->firstnode = bm->headnode;

		if ( starmod->firstnode >= loadmodel->numnodes )
		{
			ri.Sys_Error( ERR_DROP, "Inline model %i has bad firstnode", i );
		}

		VectorCopy( bm->maxs, starmod->maxs );
		VectorCopy( bm->mins, starmod->mins );
		starmod->radius = bm->radius;

		if ( i == 0 )
		{
			*loadmodel = *starmod;
		}

		starmod->numleafs = bm->visleafs;
	}
}

void
Mod_Free ( model_t *mod )
{
	Hunk_Free( mod->extradata );
	memset( mod, 0, sizeof ( *mod ) );
}

void
Mod_FreeAll ( void )
{
	int i;

	for ( i = 0; i < mod_numknown; i++ )
	{
		if ( mod_known [ i ].extradatasize )
		{
			Mod_Free( &mod_known [ i ] );
		}
	}
}
 
/*
 * Specifies the model that will be used as the world
 */
void
R_BeginRegistration ( char *model )
{
	char fullname [ MAX_QPATH ];
	cvar_t  *flushmap;

	registration_sequence++;
	r_oldviewcluster = -1; /* force markleafs */

	Com_sprintf( fullname, sizeof ( fullname ), "maps/%s.bsp", model );

	/* explicitly free the old map if different
	   this guarantees that mod_known[0] is the world map */
	flushmap = ri.Cvar_Get( "flushmap", "0", 0 );

	if ( strcmp( mod_known [ 0 ].name, fullname ) || flushmap->value )
	{
		Mod_Free( &mod_known [ 0 ] );
	}

	r_worldmodel = Mod_ForName( fullname, true );

	r_viewcluster = -1;
}

struct model_s *
R_RegisterModel ( char *name )
{
	model_t *mod;
	int i;
	dsprite_t   *sprout;
	dmdl_t      *pheader;

	mod = Mod_ForName( name, false );

	if ( mod )
	{
		mod->registration_sequence = registration_sequence;

		/* register any images used by the models */
		if ( mod->type == mod_sprite )
		{
			sprout = (dsprite_t *) mod->extradata;

			for ( i = 0; i < sprout->numframes; i++ )
			{
				mod->skins [ i ] = R_FindImage( sprout->frames [ i ].name, it_sprite );
			}
		}
		else if ( mod->type == mod_alias )
		{
			pheader = (dmdl_t *) mod->extradata;

			for ( i = 0; i < pheader->num_skins; i++ )
			{
				mod->skins [ i ] = R_FindImage( (char *) pheader + pheader->ofs_skins + i * MAX_SKINNAME, it_skin );
			}

			mod->numframes = pheader->num_frames;
		}
		else if ( mod->type == mod_brush )
		{
			for ( i = 0; i < mod->numtexinfo; i++ )
			{
				mod->texinfo [ i ].image->registration_sequence = registration_sequence;
			}
		}
	}

	return ( mod );
}

void
R_EndRegistration ( void )
{
	int i;
	model_t *mod;

	for ( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ )
	{
		if ( !mod->name [ 0 ] )
		{
			continue;
		}

		if ( mod->registration_sequence != registration_sequence )
		{  
		   	/* don't need this model */
			Mod_Free( mod );
		}
	}

	R_FreeUnusedImages();
}
 
